/*
 * Copyright (c) 2025 Li Auto Inc. and its affiliates
 * Licensed under the Apache License, Version 2.0(the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/serial/serial.h>
#include "chip.h"

#include <arch/board/board.h>

#include "arm_internal.h"

#undef uint32
#define uint32 uint32_t

#include "irq_num_E3650.h"
#include "regs_base.h"
#include "Uart_Ip_Reg.h"

/* If we are not using the serial driver for the console, then we still must
 * provide some minimal implementation of up_putc.
 */

#ifdef USE_SERIALDRIVER

/* Which UART with be tty0/console and which tty1?  The console will always
 * be ttyS0.  If there is no console then will use the lowest numbered UART.
 */

#if defined(CONFIG_UART0_SERIAL_CONSOLE) && defined(CONFIG_E3650_UART0)
#define HAVE_SERIAL_CONSOLE 1
#else
#undef CONFIG_UART0_SERIAL_CONSOLE
#undef HAVE_SERIAL_CONSOLE
#endif

#ifdef HAVE_SERIAL_CONSOLE
#if defined(CONFIG_UART0_SERIAL_CONSOLE)
#define CONSOLE_DEV g_uart0port /* UART0 is console */
#define TTYS0_DEV g_uart0port /* UART0 is ttyS0 */
#define SERIAL_CONSOLE 1
#else
#error "I'm confused... Do we have a serial console or not?"
#endif /* HAVE_SERIAL_CONSOLE */
#else
#undef CONSOLE_DEV /* No console */
#undef CONFIG_UART0_SERIAL_CONSOLE
#if defined(CONFIG_E3650_UART0)
#define TTYS0_DEV g_uart0port /* UART0 is ttyS0 */
#define SERIAL_CONSOLE 1
#else
#undef TTYS0_DEV
#endif /* CONFIG_E3650_UART0 */
#endif

#undef HAVE_UART_DEVICE
#if defined(CONFIG_E3650_UART0)
#define HAVE_UART_DEVICE 1
#define UART_FSR1_OFF 0x64U
#define UART_RXDR_OFF 0x300U
#endif /* CONFIG_E3650_UART0 */

/* Common initialization logic will not not know that the all of the UARTs
 * have been disabled.  So, as a result, we may still have to provide
 * stub implementations of tricore_earlyserialinit(),
 * tricore_serialinit(), and up_putc().
 */

struct up_dev_s {
	volatile void *uartbase; /* Base address of UART registers */
	uint8_t irq; /* IRQ associated with this UART */
	uint8_t fifo_read;
	uint8_t fifo_len;
};

/* Serial driver methods */

static int up_setup(struct uart_dev_s *dev);
static void up_shutdown(struct uart_dev_s *dev);
static int up_attach(struct uart_dev_s *dev);
static void up_detach(struct uart_dev_s *dev);
#if !defined(CONFIG_RT_FRAMEWORK) || (CONFIG_RT_FRAMEWORK == 0)
static int up_interrupt(int irq, void *context, void *arg);
#endif
static int up_ioctl(struct file *filep, int cmd, unsigned long arg);
static int up_receive(struct uart_dev_s *dev, unsigned int *status);
static void up_rxint(struct uart_dev_s *dev, bool enable);
static bool up_rxavailable(struct uart_dev_s *dev);
static void up_send(struct uart_dev_s *dev, int ch);
static void up_txint(struct uart_dev_s *dev, bool enable);
static bool up_txready(struct uart_dev_s *dev);
static bool up_txempty(struct uart_dev_s *dev);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct uart_ops_s g_uart_ops = {
	.setup = up_setup,
	.shutdown = up_shutdown,
	.attach = up_attach,
	.detach = up_detach,
	.ioctl = up_ioctl,
	.receive = up_receive,
	.rxint = up_rxint,
	.rxavailable = up_rxavailable,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
	.rxflowcontrol = NULL,
#endif
	.send = up_send,
	.txint = up_txint,
	.txready = up_txready,
	.txempty = up_txempty,
};

/* I/O buffers */

#ifdef CONFIG_E3650_UART0
static __percpu_bss char g_uart0rxbuffer[CONFIG_UART0_RXBUFSIZE];
static __percpu_bss char g_uart0txbuffer[CONFIG_UART0_TXBUFSIZE];
#define g_uart0rxbuffer this_cpu_var(g_uart0rxbuffer)
#define g_uart0txbuffer this_cpu_var(g_uart0txbuffer)
#endif

static struct up_dev_s g_uart0priv = {
	.uartbase = (volatile void *)APB_UART15_BASE,
	.irq = UART15_INTR_NUM,
	.fifo_read = 0,
	.fifo_len = 0,
};

static __percpu_data uart_dev_t g_uart0port = {
#if SERIAL_CONSOLE == 1
	.isconsole = 1,
#endif
	.recv = {
		.size = CONFIG_UART0_RXBUFSIZE,
	},
	.xmit = {
		.size = CONFIG_UART0_TXBUFSIZE,
	},
	.ops = &g_uart_ops,
	.priv = &g_uart0priv,
};
#define g_uart0port this_cpu_var(g_uart0port)

/****************************************************************************
 * Name: up_setup
 *
 * Description:
 *   Configure the UART baud, bits, parity, etc. This method is called the
 *   first time that the serial port is opened.
 *
 ****************************************************************************/

static int up_setup(struct uart_dev_s *dev)
{
	e3650_uart_init();
	return OK;
}

/****************************************************************************
 * Name: up_shutdown
 *
 * Description:
 *   Disable the UART.  This method is called when the serial
 *   port is closed
 *
 ****************************************************************************/

static void up_shutdown(struct uart_dev_s *dev)
{
	struct up_dev_s *priv = dev->priv;

	/* Disable interrupts */

	up_disable_irq(priv->irq);
}

/****************************************************************************
 * Name: up_attach
 *
 * Description:
 *   Configure the UART to operation in interrupt driven mode. This method is
 *   called when the serial port is opened.  Normally, this is just after the
 *   the setup() method is called, however, the serial console may operate in
 *   a non-interrupt driven mode during the boot phase.
 *
 *   RX and TX interrupts are not enabled by the attach method (unless the
 *   hardware supports multiple levels of interrupt enabling).  The RX and TX
 *   interrupts are not enabled until the txint() and rxint() are called.
 *
 ****************************************************************************/

static int up_attach(struct uart_dev_s *dev)
{
#if !defined(CONFIG_RT_FRAMEWORK) || (CONFIG_RT_FRAMEWORK == 0)
	struct up_dev_s *priv = dev->priv;
	int ret;

	/* Initialize interrupt generation on the peripheral */
	ret = irq_attach(priv->irq, up_interrupt, dev);
	if (ret == OK) {
		/* Enable the interrupt (RX and TX interrupts are still disabled
		 * in the UART
		 */
		if (up_cpu_index() == 0) {
			up_enable_irq(priv->irq);
		}
	}
	return ret;
#else
	return OK;
#endif
}

/****************************************************************************
 * Name: up_detach
 *
 * Description:
 *   Detach UART interrupts.  This method is called when the serial port is
 *   closed normally just before the shutdown method is called. The exception
 *   is the serial console which is never shutdown.
 *
 ****************************************************************************/

static void up_detach(struct uart_dev_s *dev)
{
	struct up_dev_s *priv = dev->priv;

	/* Disable interrupts */

	up_disable_irq(priv->irq);

	/* Detach from the interrupt */

	irq_detach(priv->irq);
}

#if defined(CONFIG_RT_FRAMEWORK) && (CONFIG_RT_FRAMEWORK == 1)
void up_uart_rx_handler(void)
{
	struct uart_dev_s *dev = &g_uart0port;
	struct up_dev_s *priv = dev->priv;

	if (up_rxavailable(dev)) {
		uart_recvchars(dev);
	}

	if (up_txready(dev)) {
		uart_xmitchars(dev);
	}

	/* clear interrupt status */

	priv->fifo_read = 0;

	putreg32(BM_INTR0_RXFWF, priv->uartbase + UART_INTR0_OFF);
}
#endif

#if !defined(CONFIG_RT_FRAMEWORK) || (CONFIG_RT_FRAMEWORK == 0)
/****************************************************************************
 * Name: up_interrupt
 *
 * Description:
 *   This is the UART interrupt handler.  It will be invoked when an
 *   interrupt is received on the 'irq'.  It should call uart_xmitchars or
 *   uart_recvchars to perform the appropriate data transfers.  The
 *   interrupt handling logic must be able to map the 'arg' to the
 *   appropriate uart_dev_s structure in order to call these functions.
 *
 ****************************************************************************/

static int up_interrupt(int irq, void *context, void *arg)
{
	struct uart_dev_s *dev = arg;
	struct up_dev_s *priv = dev->priv;

	if (up_rxavailable(dev)) {
		uart_recvchars(dev);
	}

	if (up_txready(dev)) {
		uart_xmitchars(dev);
	}

	/* clear interrupt status */

	priv->fifo_read = 0;

	putreg32(BM_INTR0_RXFWF, priv->uartbase + UART_INTR0_OFF);

	return OK;
}
#endif
/****************************************************************************
 * Name: up_ioctl
 *
 * Description:
 *   All ioctl calls will be routed through this method
 *
 ****************************************************************************/

static int up_ioctl(struct file *filep, int cmd, unsigned long arg)
{
	return -ENOTTY;
}

/****************************************************************************
 * Name: up_receive
 *
 * Description:
 *   Called (usually) from the interrupt level to receive one
 *   character from the UART.  Error bits associated with the
 *   receipt are provided in the return 'status'.
 *
 ****************************************************************************/

static int up_receive(struct uart_dev_s *dev, unsigned int *status)
{
	struct up_dev_s *priv = dev->priv;

	if (priv->fifo_len > 0) {
		priv->fifo_len--;
	}

	return (uint8_t)getreg32(priv->uartbase + UART_RXDR_OFF);
}

/****************************************************************************
 * Name: up_rxint
 *
 * Description:
 *   Call to enable or disable RX interrupts
 *
 ****************************************************************************/

static void up_rxint(struct uart_dev_s *dev, bool enable)
{
}

/****************************************************************************
 * Name: up_rxavailable
 *
 * Description:
 *   Return true if the receive register is not empty
 *
 ****************************************************************************/

static bool up_rxavailable(struct uart_dev_s *dev)
{
	struct up_dev_s *priv = dev->priv;

	if (priv->fifo_read == 0) {
		priv->fifo_len = GFV_FSR1_FILLLVL(getreg32(priv->uartbase + UART_FSR1_OFF));
		priv->fifo_read = 1;
	}

	return (priv->fifo_len != 0);
}

/****************************************************************************
 * Name: up_send
 *
 * Description:
 *   This method will send one byte on the UART.
 *
 ****************************************************************************/

static void up_send(struct uart_dev_s *dev, int ch)
{
	/* Wait for FIFO */

	if (dev == &CONSOLE_DEV) {
		up_putc(ch);
		return;
	}
}

/****************************************************************************
 * Name: up_txint
 *
 * Description:
 *   Call to enable or disable TX interrupts
 *
 ****************************************************************************/

static void up_txint(struct uart_dev_s *dev, bool enable)
{
	irqstate_t flags;

	flags = enter_critical_section();

	if (enable) {
		/* Enable the TX interrupt */

		uart_xmitchars(dev);
	}

	leave_critical_section(flags);
}

/****************************************************************************
 * Name: up_txready
 *
 * Description:
 *   Return true if the tranmsit data register is not full
 *
 ****************************************************************************/

static bool up_txready(struct uart_dev_s *dev)
{
	return true;
}

/****************************************************************************
 * Name: up_txempty
 *
 * Description:
 *   Return true if the tranmsit data register is empty
 *
 ****************************************************************************/

static bool up_txempty(struct uart_dev_s *dev)
{
	struct up_dev_s *priv = CONSOLE_DEV.priv;
	uint32_t value = getreg32(priv->uartbase + UART_FSR0_OFF);

	/* Return true if the TX wartermak is pending */

	return (0U != (value & BM_FSR0_FULL));
}

/****************************************************************************
 * Name: e3650_lowputc
 *
 * Description:
 *   Provide priority, low-level access to support OS debug writes
 *
 ****************************************************************************/

void e3650_lowputc(char ch)
{
#ifdef HAVE_SERIAL_CONSOLE
	struct up_dev_s *priv = CONSOLE_DEV.priv;
	uint32_t value;

	/* disable TX empty interrupt and transmission complete interrupt */

	value = getreg32(priv->uartbase + UART_INTEN0_OFF);
	value &= ~(BM_INTEN0_TXFWEE | BM_INTEN0_TCE);

	putreg32(value, priv->uartbase + UART_INTEN0_OFF);

	/* check Tx FIFO is not full */

	value = getreg32(priv->uartbase + UART_FSR0_OFF);

	while (0U != (value & BM_FSR0_FULL)) {
		value = getreg32(priv->uartbase + UART_FSR0_OFF);
	}
	putreg32(ch, priv->uartbase + UART_TXDR_OFF);

#endif /* HAVE_CONSOLE */
}

/****************************************************************************
 * Name: up_putc
 *
 * Description:
 *   Provide priority, low-level access to support OS debug  writes
 *
 ****************************************************************************/

void up_putc(int ch)
{
#ifdef HAVE_SERIAL_CONSOLE
	e3650_lowputc(ch);
#endif
}

/***************************************************************************
 * Name: arm_serialinit
 *
 * Description:
 *   see arm_internal.h
 *
 ***************************************************************************/

void arm_serialinit(void)
{
	/* Register the console */

#ifdef HAVE_SERIAL_CONSOLE
	CONSOLE_DEV.isconsole = true;
	uart_register("/dev/console", &CONSOLE_DEV);
#endif

	/* Register all UARTs */

	uart_register("/dev/ttyS0", &TTYS0_DEV);
}

#endif /* USE_SERIALDRIVER */

#ifdef USE_EARLYSERIALINIT
/***************************************************************************
 * Name: arm_earlyserialinit
 *
 * Description:
 *   see arm_internal.h
 *
 ***************************************************************************/

void arm_earlyserialinit(void)
{
	/* Configuration whichever one is the console */
#ifdef CONFIG_E3650_UART0
	g_uart0port.recv.buffer = g_uart0rxbuffer;
	g_uart0port.xmit.buffer = g_uart0txbuffer;
#endif

#ifdef HAVE_SERIAL_CONSOLE
	CONSOLE_DEV.isconsole = true;
	up_setup(&CONSOLE_DEV);
#endif
}
#endif /* USE_EARLYSERIALINIT */
